package com.yxsk.relay.job.admin.core.schedule.quartz;

import com.yxsk.relay.job.admin.core.schedule.RelayJobScheduleRegistrar;
import com.yxsk.relay.job.admin.core.schedule.constant.JobDataConstant;
import com.yxsk.relay.job.admin.core.schedule.lifecycle.manage.TaskTriggerManager;
import com.yxsk.relay.job.admin.core.schedule.quartz.vo.JobConfig;
import com.yxsk.relay.job.admin.data.entity.BaseEntity;
import com.yxsk.relay.job.admin.data.entity.JobApps;
import com.yxsk.relay.job.admin.data.entity.JobDetails;
import com.yxsk.relay.job.admin.data.entity.vo.QuartzJobInfo;
import com.yxsk.relay.job.admin.data.service.JobAppsService;
import com.yxsk.relay.job.admin.data.service.JobDetailService;
import com.yxsk.relay.job.admin.exception.job.RelayTaskIrrevocableException;
import com.yxsk.relay.job.component.admin.utils.SpringBeanUtils;
import com.yxsk.relay.job.component.common.exception.RelayJobRuntimeException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author 11376
 * @CreaTime 2019/6/5 13:46
 * @Description 动态任务调度配置服务
 */
@Slf4j
@AllArgsConstructor
public class QuartzDynamicScheduleService implements RelayJobScheduleRegistrar<QuartzJobInfo> {

    private Scheduler scheduler;

    private TaskTriggerManager executeManager;

    private TaskTriggerManager asyncExecuteManager;

    public TaskTriggerManager getExecuteManager() {
        return this.executeManager;
    }

    private QuartzDynamicScheduleService() {
    }

    public static class QuartzDynamicScheduleConfigServiceBuilder {

        private QuartzDynamicScheduleService scheduleService;

        public QuartzDynamicScheduleConfigServiceBuilder() {
            this.scheduleService = new QuartzDynamicScheduleService();
        }

        public QuartzDynamicScheduleConfigServiceBuilder scheduler(Scheduler scheduler) {
            this.scheduleService.scheduler = scheduler;
            return this;
        }

        public QuartzDynamicScheduleConfigServiceBuilder executeManager(TaskTriggerManager executeManager) {
            this.scheduleService.executeManager = executeManager;
            return this;
        }

        public QuartzDynamicScheduleConfigServiceBuilder asyncExecuteManager(TaskTriggerManager asyncExecuteManager) {
            this.scheduleService.asyncExecuteManager = asyncExecuteManager;
            return this;
        }

        public QuartzDynamicScheduleService build() {
            return this.scheduleService;
        }

    }

    /**
     * @param
     * @Author 11376
     * @Description 初始化注册器
     * @CreateTime 2019/6/5 18:10
     * @Return
     */
    public void init() {
        this.initExecuteManager();
        log.info("初始化执行管理器完成");

        this.reloadJob();
        log.info("加载系统任务配置完成");

        try {
            this.scheduler.start();
            log.info("Quartz任务调度器启动成功");
        } catch (SchedulerException e) {
            log.error("Quartz任务调度器启动错误", e);
            throw new RelayJobRuntimeException(e);
        }
    }

    private void initExecuteManager() {
        if (this.executeManager == null) {
            this.executeManager = new TaskTriggerManager.DefaultTaskTriggerManager();
        }
        if (this.asyncExecuteManager == null) {
            this.asyncExecuteManager  = new TaskTriggerManager.DefaultTaskTriggerManager();
        }
    }

    /**
     * @param
     * @Author 11376
     * @Description 销毁注册器
     * @CreateTime 2019/6/5 18:10
     * @Return
     */
    public void destroy() {
        try {
            this.scheduler.clear();
            this.scheduler.shutdown();
            log.info("Quartz任务调度器已关闭");
        } catch (SchedulerException e) {
            log.error("Quartz任务调度器关闭错误", e);
            throw new RelayJobRuntimeException(e);
        }
    }

    @Override
    public void reloadJob() {
        // 先清空调度服务中的配置信息
        try {
            this.scheduler.clear();
        } catch (SchedulerException e) {
            throw new RelayJobRuntimeException(e);
        }
        JobDetailService jobDetailService = SpringBeanUtils.getBean(JobDetailService.class);
        JobAppsService appsService = SpringBeanUtils.getBean(JobAppsService.class);
        List<JobDetails> detailsList = jobDetailService.findAllNormalEntities();
        if (!CollectionUtils.isEmpty(detailsList)) {
            detailsList.stream().forEach(details -> {
                if (StringUtils.hasLength(details.getCronExpression())) {
                    QuartzJobInfo jobConfig = new QuartzJobInfo();
                    jobConfig.setId(details.getId());
                    jobConfig.setHandlerName(details.getHandlerName());
                    // 获取应用名称
                    JobApps jobApps = appsService.findById(details.getAppId());
                    if (jobApps == null || BaseEntity.DelFlagEnum.DELETED.equals(jobApps.getDelFlag())) {
                        throw new RelayJobRuntimeException("未找到任务相关应用信息");
                    }
                    jobConfig.setAppName(jobApps.getAppName());
                    jobConfig.setCron(details.getCronExpression());
                    try {
                        jobConfig.setScheduleTaskClass((Class<? extends Job>) Class.forName(details.getTriggerClassName()));
                    } catch (ClassNotFoundException e) {
                        throw new RelayJobRuntimeException(e);
                    }
                    jobConfig.setExecuteModel(JobConfig.ExecuteModel.getExecuteModel(details.getExecuteModel()));
                    jobConfig.setRouteStrategy(JobConfig.RouteStrategy.getStrategy(details.getRouteStrategy()));
                    jobConfig.setBlockStrategy(JobConfig.BlockStrategy.getBlockStrategy(details.getBlockStrategy()));
                    jobConfig.setStatus(JobConfig.JobStatus.getJobStatus(details.getStatus()));

                    this.registerJob(jobConfig);
                }
            });
        }
    }

    /**
     * @param jobConfig
     * @Author 11376
     * @Description 注册任务
     * @CreateTime 2019/6/5 18:11
     * @Return
     */
    @Override
    public void registerJob(QuartzJobInfo jobConfig) {
        if (log.isDebugEnabled()) {
            log.debug("注册任务, Job：{}", jobConfig);
        }
        TriggerKey triggerKey = this.getTriggerKey(jobConfig);
        try {
            // 如果服务已注册返回
            if (scheduler.checkExists(triggerKey)) {
                if (log.isDebugEnabled()) {
                    log.debug("注册任务已存在, 无需重复添加, 任务Id：{}", jobConfig.getId());
                }
                return;
            }
        } catch (SchedulerException e) {
            throw new RelayJobRuntimeException(e);
        }

        JobDetail jobDetail = getJobDetail(jobConfig);

        try {
            if (StringUtils.hasLength(jobConfig.getCron())) {
                scheduler.scheduleJob(jobDetail, getCronTrigger(jobConfig));
            } else {
                scheduler.addJob(jobDetail, true, true);
            }
            if (log.isDebugEnabled()) {
                log.debug("注册任务成功, 任务Id：{}, cron：{}", jobConfig.getId(), jobConfig.getCron());
            }
        } catch (SchedulerException e) {
            log.error("注册任务失败, Job：" + jobConfig, e);
            throw new RelayJobRuntimeException(e);
        }
    }

    /**
     * @param
     * @Author 11376
     * @Description 注入执行管理器
     * @CreateTime 2019/6/5 20:13
     * @Return
     */
    private JobDataMap getJobDataMap() {
        Map<String, Object> data = new HashMap<>();
        data.put(JobDataConstant.TASK_EXECUTE_MANAGER, this.executeManager);
        data.put(JobDataConstant.TASK_ASYNC_EXECUTE_MANAGER, this.asyncExecuteManager);
        return new JobDataMap(data);
    }

    @Override
    public void updateJob(QuartzJobInfo jobConfig) {
        if (log.isDebugEnabled()) {
            log.debug("任务配置更新, Job：{}", jobConfig);
        }
        if (StringUtils.hasLength(jobConfig.getCron())) {
            TriggerKey triggerKey = this.getTriggerKey(jobConfig);
            CronTrigger trigger = getCronTrigger(jobConfig);

            try {
                Trigger schedulerTrigger = this.scheduler.getTrigger(triggerKey);
                if (schedulerTrigger instanceof CronTrigger) {
                    scheduler.rescheduleJob(triggerKey, trigger);
                    if (log.isDebugEnabled()) {
                        log.debug("任务配置更新成功, Job：{}", jobConfig);
                    }
                    return;
                }
            } catch (SchedulerException e) {
                log.error("任务配置更新失败, Job：" + jobConfig, e);
                throw new RelayJobRuntimeException(e);
            }
        }

        // 先卸载任务, 在注册任务
        this.unloadJob(jobConfig);
        this.registerJob(jobConfig);
    }

    @Override
    public void unloadJob(QuartzJobInfo jobConfig) {
        if (log.isDebugEnabled()) {
            log.debug("删除任务, Job：{}", jobConfig);
        }
        JobKey jobKey = getJobKey(jobConfig);
        if (existJob(jobKey)) {
            try {
                scheduler.deleteJob(jobKey);
                if (log.isDebugEnabled()) {
                    log.debug("删除任务成功, Job：{}", jobConfig);
                }
            } catch (SchedulerException e) {
                log.error("删除任务失败, Job：{}" + jobConfig, e);
                throw new RelayJobRuntimeException(e);
            }
        }
    }

    @Override
    public void runNow(QuartzJobInfo jobConfig, Map<String, Object> param) {
        if (log.isDebugEnabled()) {
            log.debug("执行任务[soon], Job：{}", jobConfig);
        }
        try {
            JobKey jobKey = this.getJobKey(jobConfig);
            if (!scheduler.checkExists(jobKey)) {
                this.registerJob(jobConfig);
            }
            if (CollectionUtils.isEmpty(param)) {
                scheduler.triggerJob(jobKey);
            } else {
                JobDataMap dataMap = new JobDataMap();
                dataMap.putAll(param);
                scheduler.triggerJob(jobKey, dataMap);
            }
            if (log.isDebugEnabled()) {
                log.debug("任务执行[soon], Job：{}", jobConfig);
            }
        } catch (SchedulerException e) {
            log.error("任务执行异常[soon], Job：" + jobConfig, e);
            throw new RelayJobRuntimeException(e);
        }
    }

    @Override
    public void cancelExecute(String executeId) throws RelayTaskIrrevocableException {
        throw new RelayTaskIrrevocableException("Cancellation not supported.");
    }

    @Override
    public void pauseJob(QuartzJobInfo jobConfig) {
        if (log.isDebugEnabled()) {
            log.debug("暂停任务, Job：{}", jobConfig);
        }
        JobKey jobKey = getJobKey(jobConfig);
        if (existJob(jobKey)) {
            try {
                scheduler.pauseJob(jobKey);
                if (log.isDebugEnabled()) {
                    log.debug("暂停任务成功, Job：{}", jobConfig);
                }
            } catch (SchedulerException e) {
                log.error("暂停任务错误, Job：" + jobConfig, e);
                throw new RelayJobRuntimeException(e);
            }
        }
    }

    @Override
    public void pauseAllJob() {
        if (log.isDebugEnabled()) {
            log.debug("暂停所有任务");
        }
        try {
            this.scheduler.pauseAll();
            if (log.isDebugEnabled()) {
                log.debug("暂停任务完成");
            }
        } catch (SchedulerException e) {
            log.error("暂停所有任务错误", e);
            throw new RelayJobRuntimeException(e);
        }
    }

    @Override
    public void resumeJob(JobConfig jobConfig) {
        if (log.isDebugEnabled()) {
            log.debug("恢复暂停任务, Job：{}", jobConfig);
        }
        JobKey jobKey = this.getJobKey(jobConfig);
        if (existJob(jobKey)) {
            try {
                this.scheduler.resumeJob(jobKey);
                if (log.isDebugEnabled()) {
                    log.debug("恢复任务成功, Job：{}", jobConfig);
                }
            } catch (SchedulerException e) {
                log.error("恢复任务错误, Job：" + jobConfig, e);
                throw new RelayJobRuntimeException(e);
            }
        }
    }

    @Override
    public void resumeAllJob() {
        if (log.isDebugEnabled()) {
            log.debug("恢复所有任务");
        }
        try {
            this.scheduler.resumeAll();
            if (log.isDebugEnabled()) {
                log.debug("恢复所有任务成功");
            }
        } catch (SchedulerException e) {
            log.error("恢复所有任务错误", e);
            throw new RelayJobRuntimeException(e);
        }
    }

    private boolean existJob(JobKey jobKey) {
        try {
            return scheduler.getJobDetail(jobKey) != null;
        } catch (SchedulerException e) {
            throw new RelayJobRuntimeException(e);
        }
    }

    private JobKey getJobKey(JobConfig jobConfig) {
        return new JobKey(jobConfig.getId(), buildJobGroup(jobConfig.getAppName(), jobConfig.getHandlerName()));
    }

    private TriggerKey getTriggerKey(JobConfig jobConfig) {
        return new TriggerKey(jobConfig.getId(), buildJobGroup(jobConfig.getAppName(), jobConfig.getHandlerName()));
    }

    private JobDetail getJobDetail(JobConfig jobConfig) {
        return JobBuilder.newJob(jobConfig.getScheduleTaskClass())
                .withIdentity(jobConfig.getId(), buildJobGroup(jobConfig.getAppName(), jobConfig.getHandlerName()))
                .setJobData(getJobDataMap())
                .build();
    }

    private CronTrigger getCronTrigger(JobConfig jobConfig) {
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobConfig.getCron());
        return TriggerBuilder.newTrigger()
                .withIdentity(jobConfig.getId(), buildJobGroup(jobConfig.getAppName(), jobConfig.getHandlerName()))
                .withSchedule(cronScheduleBuilder)
                .build();
    }

    private Trigger getSimpleTrigger(JobConfig jobConfig) {
        return TriggerBuilder.newTrigger()
                .withIdentity(jobConfig.getId(), buildJobGroup(jobConfig.getAppName(), jobConfig.getHandlerName()))
                .build();
    }

    /**
     * @param appName
     * @param handlerName
     * @Author 11376
     * @Description 构建 quartz 的工作组， 规则：Relay-appName[handlerName]
     * @CreateTime 2019/6/16 10:49
     * @Return
     */
    private String buildJobGroup(String appName, String handlerName) {
        return new StringBuilder("Relay-").append(appName).append("[").append(handlerName).append("]").toString();
    }

}
